// 
// Copyright (c) 2006-2009 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace Anculus.Core
{
	//TODO: Save method, with bool includeKey
	public sealed class AesKey
	{
		private ByteBuffer iv;
		
		//TODO: support for key.Length != standard --> key generation
		//TODO: method to regenerate the IV
		
		//TODO: overload with Key, IV
		
		public AesKey (int bitsize, ByteBuffer key)
		{
			if (key == null)
				throw new ArgumentNullException ("key");
			if (bitsize != 128 && bitsize != 256 && bitsize != 192)
				throw new ArgumentException ("size must be 128, 192 or 256 bits (16, 24 or 32 bytes).");
			
			KeySize = bitsize;
			Key = Crypto.CopyDataToSize (key, bitsize / 8);
		}
		
		public AesKey (ByteBuffer key)
			: this (128, key)
		{
		}
		
		public AesKey (int bitsize)
		{
			if (bitsize != 128 && bitsize != 256 && bitsize != 192)
				throw new ArgumentException ("size must be 128, 192 or 256 bits (16, 24 or 32 bytes).");
			
			KeySize = bitsize;
			Key = new byte[bitsize / 8];

			RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider ();
			rand.GetBytes (Key.Raw);
		}

		public AesKey ()
			: this (128)
		{
		}
		
		public ByteBuffer Key { get; protected set; }

		public ByteBuffer Iv
		{
			get {
				if (iv == null) {
					//generate random IV
					iv = new ByteBuffer (16);

					RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider ();
					rand.GetBytes (iv.Raw);
				}
				return iv;
			}
			set {
				if (value == null || value.Length != 16)
					throw new ArgumentException ("IV length must be 128 bits (16 byte).");
				iv = Crypto.CopyDataToSize (value, 16); //128 bit
			}
		}
		
		public bool HasIv
		{
			get { return iv == null; }
		}
		
		public int KeySize { get; protected set; }
		
		public ICryptoTransform CreateEncryptor ()
		{
			Rijndael aes = Rijndael.Create ();
			aes.Mode = CipherMode.CBC;
			
			return aes.CreateEncryptor (Key.Raw, Iv.Raw);
		}
		
		public ICryptoTransform CreateDecryptor ()
		{
			Rijndael aes = Rijndael.Create ();
			aes.Mode = CipherMode.CBC;
			
			return aes.CreateDecryptor (Key.Raw, Iv.Raw);
		}
		
		public static implicit operator AesKey (string password) 
		{
			if (string.IsNullOrEmpty (password))
				throw new ArgumentException ("password");
			
			return new AesKey (128, password);
		}

		public static implicit operator AesKey (ByteBuffer data) 
		{
			if (data == null)
				throw new ArgumentNullException ("data");
			
			return new AesKey (data.Length * 8, data);
		}
	}
}